Release 10.1A: OpenEdge Development:
Progress 4GL Handbook
Using the UNDO statement in the sample procedure
In this section, you see how the
UNDOstatement can affect the operation of thesaveOrderprocedure inorderlogic.p. You’ll try several variations to the business logic, in succession, to illustrate the ways you can control the scope of your transaction and how to react when it fails.Using UNDO, LEAVE on a block
In this section, you try an example that uses
UNDO,LEAVEon a block.
![]()
To undo and leave the block that updates the OrderLine table:
- To make this a transaction block of its own, put the
ENDstatement for theDO TRANSACTIONblock after the Order is updated, as you did earlier:
This makes the
FOR EACHblock that follows a separate transaction.- Add a new variable definition in
saveOrderfor an error message to return:
- Add the highlighted lines, shown below, to the
FOR EACHblock that updates OrderLines:
This code checks to make sure that the ExtendedPrice for an OrderLine is not increased by more than 20%. If this limit is exceeded, then the current iteration of the block is undone and the block is left.
On each iteration, the
FOR EACHblock makes a ttOline record current. Your code uses the second buffer,bUpdateOline, to locate the updated version of that OrderLine temp-table record. It then finds the OrderLine in the database and copies the updates to it. Next it releases the database record to force itsWRITEtrigger to fire, which recalculates the ExtendedPrice field. Then it again finds the database record and copies it back into thebUpdateOlinebuffer to capture the effects of the trigger code, in particular the new ExtendedPrice value. Only now can your program logic compare this to the original value in thettOlinebuffer to see if it exceeds the limit. If it does, then you store off a message, then undo and leave theFOR EACHblock.At this point, following the
UNDOstatement, the whole database change that you wrote out and then read back in is gone—Progress has restored the database record to its values before the transaction began.- Add code so that after leaving the block, you check if you have an error message. If so, your code needs to re-find the OrderLine record with its original values, and copy it back into the
bUpdateOlinebuffer to return to the client for display. It then returns the message as the return value for the procedure:
Why did you have to re-find the OrderLine record from the database? The
UNDOreleased it from its buffer, so it’s no longer available after the block ends. Then why did you not have to re-find the temp-table record? You defined the temp-table asNO-UNDOso it was left out of normal transaction handling. The temp-table buffer is scoped to the whole procedure, so the record it contains is still available after the block ends. If you had defined the temp-table withoutNO-UNDO, then the bUpdateOline record would have been released, as well as the database OrderLine record, and you would have had to re-establish it in the temp-table as well. This is an illustration of the kind of overhead you save by usingNO-UNDOtemp-tables, and also of the sometimes unintended consequences of having undo capability on a temp-table that doesn’t require it.The simple diagram in Figure 17–2 illustrates the scope of the transactions.
Figure 17–2: Example transaction scope
![]()
In Figure 17–2, the first transaction, which saves changes to the Order, completes at the
ENDstatement for theDO TRANSACTIONblock. At the end of theFOR EACHblock, the transaction to save the current OrderLine ends, committing those changes to the database, releasing the OrderLine record, and then going back to the beginning of the block. Each OrderLine is saved in a separate transaction.The
UNDOstatement rolls back the transaction for the current OrderLine and leaves theFOR EACHblock, which skips any remaining ttOline records. But any previously committed OrderLine changes remain in the database. For example, in Figure 17–3, the user changes the Price for Line 1 from 7.49 to 7.60, for Line 2 from 23.00 to 30.00, and for Line 3 from 13.98 to 13.50.Figure 17–3: Order Updates example
![]()
The first and third changes are valid, but the second one is not. It increases the ExtendedPrice by too much. So when the user clicks Save, the user sees an error message for Line 2, as shown in Figure 17–4.
Figure 17–4: Order Updates example message
![]()
But if the user presses the Fetch button to refresh all the OrderLines:
Figure 17–5 shows the result.
Figure 17–5: Order Updates example (after Fetch)
![]()
This might not be the behavior you want.
On the one hand, you might want all OrderLines to be committed together or all undone if any one fails. In another case, you might want to process each OrderLine as a separate transaction, but keep going if one of them fails. Look at both of those cases.
In the first case, you want all the OrderLine updates to either succeed or fail together. If any one fails its validation, all are rolled back.
![]()
To see an example of the first case:
- Define the transaction scope to be greater than a single iteration of the
FOR EACHblock by putting a newDO TRANSACTIONblock around it. Then add a label for that new block to identify how much to undo in yourUNDOstatement:
- Change the
UNDOstatement to undo the entire larger transaction and to leave that outer block as well:
Remember that the default is to undo and leave the innermost block with the error property, the
FOR EACHblock.- Add another
ENDstatement to match the newDO TRANSACTIONstatement that begins the new block:
Note that a block label, such as OlineBlock:, does not require a matching
ENDstatement. It is simply an identifier for a place in the code and does not actually start a block of its own.- Make a change to the code that restores the original values to the temp-table if there’s an error. Because the error might not be on the line that’s current when you leave the block, you need to re-read all the OrderLines for the Order and buffer-copy their values back into the update copies of each of the temp-table records, in a
FOR EACHloop:
Now if you make changes to three OrderLines, and the second of the three is invalid, then all three changes are rolled back because they’re all part of one large transaction. You see this reflected in your window.
Figure 17–6 shows a sketch of what this variation looks like.
Figure 17–6: Variation of transaction scope
![]()
Now look at the second case. You’d like each OrderLine committed independently of the others, and you’d like to keep going if one is invalid. In this case, you can use the
NEXToption on theUNDOstatement instead ofLEAVE. If an error is found, the current transaction is undone and your code continues on to the next ttOline record.
![]()
To try this variation:
- Remove the OlineBlock block label, along with the
DO TRANSACTIONblock header and its matchingENDstatement, from around theFOR EACHblock.- Change the
UNDO,LEAVEstatement toUNDO, NEXT.Since it is now possible to get errors on more than one OrderLine at a time, you should be prepared to accumulate multiple error messages in your message string.
- Append each new message to the end of the string by using the plus sign (+) as a concatenation symbol for the character variable (
cMessage = cMessage + . . .).- Put a newline character at the end of each message, using the
CHRfunction to append the ASCII character whose numeric value is 10 to the string:
- Run the window.
- Enter a valid price for Line 1 and invalid prices for Lines 2 and 3. You see error messages for both of these:
![]()
You can also see that the valid change for Line 1 is kept because you’re back to making each iteration of the
FOR EACHblock its own transaction.Figure 17–7 shows a sketch of this variation.
Figure 17–7: Another variation of transaction scope
![]()
|
Copyright © 2005 Progress Software Corporation www.progress.com Voice: (781) 280-4000 Fax: (781) 280-4095 |